/*   The MIT License
 *   
 *   Carina Common
 *   Copyright (c) 2010-2011 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef _CARINA_LOGGING_HH
#define _CARINA_LOGGING_HH

#include "carina/common/global.hh"
#include "carina/common/patterns.hh"
#include "carina/common/timer.hh"
#include "carina/common/containers.hh"

#include <fstream>

namespace Carina
{
enum LoggingLevel
{
    CE_LOG_UNKNOWN,
    CE_LOG_INFO,
    CE_LOG_DEBUG,
    CE_LOG_WARNING,
    CE_LOG_ERROR,
    CE_LOG_FATAL
};

// TODO: Asynchronous write
class Log: public Singleton<Log>
{
    struct LogMessage
    {
        LoggingLevel    level;
        nanosec         timestamp;
        string          message;
		LogMessage(LoggingLevel _level, nanosec ts, string msg)
			:	level(_level),
				timestamp(ts),
				message(msg) {}
    };
    
    Timer               m_Timer;
    std::fstream        m_LogFile;
    LoggingLevel        m_MinLoggingLevel,
                        m_CurrentLoggingLevel;
    nanosec             m_Timestamp;
    typedef DataVector<LogMessage> LogMessages;
    LogMessages         m_LogMessages;
    size_t              m_CurrentIndex;
    std::stringstream   m_MessageBuffer;
public:
    Log();
    Log(const string& name, LoggingLevel log_level = CE_LOG_INFO);
     ~Log();
    
    template<typename T>
    Log& operator<<(const T& t)
    {
        if(m_CurrentLoggingLevel >= m_MinLoggingLevel)
            m_MessageBuffer << t;
        return *this;
    }

    Log& operator<<(std::ostream& (*fn)(std::ostream&))
    {
        if(m_CurrentLoggingLevel >= m_MinLoggingLevel)
            fn(m_MessageBuffer);
        return *this;
    }

    void flush();
    
    void setLogFile(const string& filename);
    
    void setMinLoggingLevel(LoggingLevel log_level);
    LoggingLevel getCurrentLoggingLevel() { return m_CurrentLoggingLevel; }
    LoggingLevel getMinLoggingLevel();

    Log& logStream(LoggingLevel log_level);
    void logMessage(LoggingLevel log_level, const string& msg);
    string readLog();

    inline static Log& stream(LoggingLevel log_level) { return Log::getSingleton().logStream(log_level); }
    inline static void message(LoggingLevel log_level, const string& msg) { return Log::getSingleton().logMessage(log_level, msg); }
    inline static string read() { return Log::getSingleton().readLog(); }
private:
    std::ostream& getOutputStream();
	void flushStream(std::ostream& os);
    void printMessage(std::ostream& _stream, const LogMessage& msg);
    void flushCurrentMessage();
};

class LogStream
{
    Log&         m_Log;
    LoggingLevel m_LoggingLevel;
public:
    LogStream(Log& log)
        :   m_Log(log),
            m_LoggingLevel(log.getCurrentLoggingLevel()) {}

    template<typename T>
    LogStream& operator<<(const T& t)
    {
        if(m_LoggingLevel != m_Log.getCurrentLoggingLevel())
            m_Log.logStream(m_LoggingLevel) << t;
        else
            m_Log << t;
        return *this;
    }

    LogStream& operator<<(std::ostream& (*fn)(std::ostream&))
    {
        if(m_LoggingLevel != m_Log.getCurrentLoggingLevel())
            m_Log.logStream(m_LoggingLevel) << fn;
        else
            m_Log << fn;
        return *this;
    }
};

}

#endif /* _CARINA_LOGGING_HH */